package com.wcj.study.multiredis.config;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.wcj.study.multiredis.config.MultiRedisProperties.RedisProps;
import com.wcj.study.multiredis.config.MultiRedisProperties.RedisProps.Cluster;
import com.wcj.study.multiredis.config.MultiRedisProperties.RedisProps.Sentinel;

import redis.clients.jedis.JedisPoolConfig;

/**
 * redis连接创建类
 * 
 * @author 汪长军 Email：changjunwang@xinhe99.com
 * @date 2018-12-12 下午4:19:48
 * @copyright 信和上融网络科技(北京)有限公司
 */
public class MultiRedisConnectionFactory {

    /*
     * 配置属性
     */
    private MultiRedisProperties properties;

    /**
     * 哨兵配置属性
     */
    private Map<String, RedisSentinelConfiguration> sentinelConfigurationMap = new HashMap<>();

    /**
     * 集群配置属性
     */
    private Map<String, RedisClusterConfiguration> clusterConfigurationMap = new HashMap<>();

    public MultiRedisConnectionFactory(MultiRedisProperties properties) {
        this.properties = properties;
    }

    /**
     * 
     * 创建redis连接
     *
     * @return
     * @throws UnknownHostException
     */
    public Map<String, JedisConnectionFactory> createRedisConnectionFactory() throws UnknownHostException {

        Map<String, JedisConnectionFactory> jedisConnectionFactoryMap = new HashMap<>();

        for (Entry<String, RedisProps> obj : properties.getInstance().entrySet()) {
            RedisProps properties = obj.getValue();
            String key = obj.getKey();
            JedisConnectionFactory jedisConnectionFactory =
                applyProperties(createJedisConnectionFactory(key, properties), properties);
            jedisConnectionFactory.afterPropertiesSet();
            jedisConnectionFactoryMap.put(key, jedisConnectionFactory);
        }

        return jedisConnectionFactoryMap;
    }

    protected final JedisConnectionFactory applyProperties(JedisConnectionFactory factory, RedisProps properties) {
        configureConnection(factory, properties);
        if (properties.isSsl()) {
            factory.setUseSsl(true);
        }
        factory.setDatabase(properties.getDatabase());
        if (properties.getTimeout() > 0) {
            factory.setTimeout(properties.getTimeout());
        }
        return factory;
    }

    private void configureConnection(JedisConnectionFactory factory, RedisProps properties) {
        if (StringUtils.hasText(properties.getUrl())) {
            configureConnectionFromUrl(factory, properties);
        } else {
            factory.setHostName(properties.getHost());
            factory.setPort(properties.getPort());
            if (properties.getPassword() != null) {
                factory.setPassword(properties.getPassword());
            }
        }
    }

    private void configureConnectionFromUrl(JedisConnectionFactory factory, RedisProps properties) {
        String url = properties.getUrl();
        if (url.startsWith("rediss://")) {
            factory.setUseSsl(true);
        }
        try {
            URI uri = new URI(url);
            factory.setHostName(uri.getHost());
            factory.setPort(uri.getPort());
            if (uri.getUserInfo() != null) {
                String password = uri.getUserInfo();
                int index = password.indexOf(":");
                if (index >= 0) {
                    password = password.substring(index + 1);
                }
                factory.setPassword(password);
            }
        } catch (URISyntaxException ex) {
            throw new IllegalArgumentException("Malformed 'spring.redis.url' " + url, ex);
        }
    }

    protected final RedisSentinelConfiguration getSentinelConfig(String key, RedisProps properties) {

        if (this.sentinelConfigurationMap.get(key) != null) {
            return this.sentinelConfigurationMap.get(key);
        }

        Sentinel sentinelProperties = properties.getSentinel();
        if (sentinelProperties != null) {
            RedisSentinelConfiguration config = new RedisSentinelConfiguration();
            config.master(sentinelProperties.getMaster());
            config.setSentinels(createSentinels(sentinelProperties));
            this.sentinelConfigurationMap.put(key, config);
            return config;
        }

        return null;
    }

    /**
     * Create a {@link RedisClusterConfiguration} if necessary.
     * 
     * @return {@literal null} if no cluster settings are set.
     */
    protected final RedisClusterConfiguration getClusterConfiguration(String key, RedisProps properties) {
        if (this.clusterConfigurationMap.get(key) != null) {
            return this.clusterConfigurationMap.get(key);
        }
        if (properties.getCluster() == null) {
            return null;
        }
        Cluster clusterProperties = properties.getCluster();
        RedisClusterConfiguration config = new RedisClusterConfiguration(clusterProperties.getNodes());

        if (clusterProperties.getMaxRedirects() != null) {
            config.setMaxRedirects(clusterProperties.getMaxRedirects());
        }

        this.clusterConfigurationMap.put(key, config);

        return config;
    }

    private List<RedisNode> createSentinels(Sentinel sentinel) {
        List<RedisNode> nodes = new ArrayList<RedisNode>();
        for (String node : StringUtils.commaDelimitedListToStringArray(sentinel.getNodes())) {
            try {
                String[] parts = StringUtils.split(node, ":");
                Assert.state(parts.length == 2, "Must be defined as 'host:port'");
                nodes.add(new RedisNode(parts[0], Integer.valueOf(parts[1])));
            } catch (RuntimeException ex) {
                throw new IllegalStateException("Invalid redis sentinel " + "property '" + node + "'", ex);
            }
        }
        return nodes;
    }

    private JedisConnectionFactory createJedisConnectionFactory(String key, RedisProps properties) {

        JedisPoolConfig poolConfig = properties.getPool() != null ? jedisPoolConfig(properties) : new JedisPoolConfig();

        if (getSentinelConfig(key, properties) != null) {
            return new JedisConnectionFactory(getSentinelConfig(key, properties), poolConfig);
        }
        if (getClusterConfiguration(key, properties) != null) {
            return new JedisConnectionFactory(getClusterConfiguration(key, properties), poolConfig);
        }
        return new JedisConnectionFactory(poolConfig);
    }

    private JedisPoolConfig jedisPoolConfig(RedisProps properties) {
        JedisPoolConfig config = new JedisPoolConfig();
        RedisProps.Pool props = properties.getPool();
        config.setMaxTotal(props.getMaxActive());
        config.setMaxIdle(props.getMaxIdle());
        config.setMinIdle(props.getMinIdle());
        config.setMaxWaitMillis(props.getMaxWait());
        return config;
    }

}
